class Vue {
    // 构造函数
    constructor(obj_instance) {
        // 获取html文件内写入的data对象值
        this.$data = obj_instance.data;
        // 劫持用户在html里书写的data对象值
        Observer(this.$data);
        // 解析 el 对应的 div 标签里的模板
        Compile(obj_instance.el, this);
    }
}

function Observer(data_instance) {
    // 设置递归的出口
    if (!data_instance || typeof data_instance !== 'object') {
        return ;
    }
    // 创建发布者对象
    const dependency = new Dependency();
    // 递归的主体
    Object.keys(data_instance).forEach(key => {
        let value = data_instance[key];
        // 递归的入口
        Observer(value);
        // 对象代理
        Object.defineProperty(data_instance, key, {
            enumerable: true,
            configurable: true,
            get() {
                console.log(`访问了属性: ${key} -> 值: ${value}`);
                // 订阅者要加入发布者实例的数组
                Dependency.temp && dependency.addSub(Dependency.temp);
                console.log(Dependency.temp);
                return value;
            },
            set(newValue) {
                console.log(`属性 ${key} 的值 ${value} 修改为: ${newValue}`);
                value = newValue;
                Observer(newValue);
                dependency.notify();
            },
        });
    });
}

function Compile(element, vm) {
    // 获取页面里我们要操作的app对象
    vm.$el = document.querySelector(element);
    // 创建文档碎片
    const fragment = document.createDocumentFragment();
    let child;
    while (child = vm.$el.firstChild) {
        fragment.append(child);
    }
    // 替换文档碎片的内容
    (function fragment_compile(node) {
        // 正则表达式 - 匹配模板字符串
        const pattern = /\{\{\s*(\S+)\s*\}\}/;
        // 目标是 节点类型为3 的节点
        if (node.nodeType === 3) {
            const xxx = node.nodeValue;
            const result_regex = pattern.exec(node.nodeValue);
            // 匹配成功时输出匹配结果
            if (result_regex) {
                const arr = result_regex[1].split('.');
                const value = arr.reduce((total, current) => {
                    return total[current];
                }, vm.$data);
                node.nodeValue = xxx.replace(pattern, value);
                // 创建订阅者
                new Watcher(vm, result_regex[1], newValue => {
                    node.nodeValue = xxx.replace(pattern, newValue);
                });
            }
            return ;
        } else if (node.nodeType === 1 && node.nodeName === 'INPUT') {
            const attr = Array.from(node.attributes);
            attr.forEach(i => {
                if (i.nodeName === 'v-model') {
                    const value = i.nodeValue.split('.').reduce(
                        (total, current) => total[current], vm.$data
                    );
                    node.value = value;
                    new Watcher(vm, i.nodeValue, newValue => {
                        node.value = newValue;
                    });
                    node.addEventListener('input', (e) => {
                        const arr1 = i.nodeValue.split('.');
                        const arr2 = arr1.slice(0, arr1.length-1);
                        const final = arr2.reduce(
                            (total, current) => total[current], vm.$data
                        );
                        final[arr1[arr1.length-1]] = e.target.value;
                    });
                }
            });
        }
        // 递归遍历所有节点
        node.childNodes.forEach((child) => { fragment_compile(child); });
    })(fragment);
    // 将替换完的文本渲染到页面里
    vm.$el.appendChild(fragment);
}

class Watcher {
    constructor(vm, key, callback) {
        this.vm = vm;
        this.key = key;
        this.callback = callback;
        // 临时属性 - 触发 getter
        Dependency.temp = this;
        key.split('.').reduce((total, current) => total[current], vm.$data);
        Dependency.temp = null;
    }
    update() {
        const value = this.key.split('.').reduce(
            (total, current) => total[current], this.vm.$data
        );
        this.callback(value);
    }
}

class Dependency {
    constructor() {
        this.subscribers = [];
    }
    addSub(sub) {
        // 添加订阅者的方法
        this.subscribers.push(sub);
    }
    notify() {
        // 通知所有订阅者的方法
        this.subscribers.forEach(sub => sub.update());
    }
}
